package ru.passivemoney.manage.orders;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dukascopy.api.IOrder;
import com.dukascopy.api.Instrument;

public class OrdersHolder {
	
	private static final Logger logger = LoggerFactory.getLogger(OrdersHolder.class);
	
	private static final OrderList OPENED_ORDERS = new OrderList("OPENED_ORDERS");
	private static final OrderList CREATED_ORDERS = new OrderList("CREATED_ORDERS");
	private static final OrderList TO_OPEN_ORDERS = new OrderList("TO_OPEN_ORDERS");
	private static final OrderList TO_CLOSE_ORDERS = new OrderList("TO_CLOSE_ORDERS");
	
	public static final void clearOpenedOrders(){
		OPENED_ORDERS.clear();
	}
	
	public static final void putToOpenedOrders(Instrument instrument, IOrder order){
		OPENED_ORDERS.putOrder(instrument, order);
	}
	
	public static final void putToOpenedOrders(Instrument instrument, List<IOrder> orders){
		OPENED_ORDERS.putOrders(instrument, orders);
	}
	
	public static final List<IOrder> getOpenedOrders(Instrument instrument){
		return OPENED_ORDERS.getOrders(instrument);
	}
	
	public static final void releaseFlagForOpenedOrders(Instrument instrument){
		OPENED_ORDERS.releaseFlag(instrument);
	}
	
	public static final int getOpenedOrdersCount(){
		return OPENED_ORDERS.getOrdersCount();
	}
	
	public static final void clearCreatedOrders(){
		CREATED_ORDERS.clear();
	}
	
	public static final void putToCreatedOrders(Instrument instrument, IOrder order){
		CREATED_ORDERS.putOrder(instrument, order);
	}
	
	public static final void putToCreatedOrders(Instrument instrument, List<IOrder> orders){
		CREATED_ORDERS.putOrders(instrument, orders);
	}
	
	public static final List<IOrder> getCreatedOrders(Instrument instrument){
		return CREATED_ORDERS.getOrders(instrument);
	}
	
	public static final void releaseFlagForCreatedOrders(Instrument instrument){
		CREATED_ORDERS.releaseFlag(instrument);
	}
	
	public static final int getCreatedOrdersCount(){
		return CREATED_ORDERS.getOrdersCount();
	}
	
	public static final void clearToOpenOrders(){
		TO_OPEN_ORDERS.clear();
	}
	
	public static final void putToToOpenOrders(Instrument instrument, IOrder order){
		TO_OPEN_ORDERS.putOrder(instrument, order);
	}
	
	public static final void putToToOpenOrders(Instrument instrument, List<IOrder> orders){
		TO_OPEN_ORDERS.putOrders(instrument, orders);
	}
	
	public static final void releaseFlagForToOpenOrders(Instrument instrument){
		TO_OPEN_ORDERS.releaseFlag(instrument);
	}
	
	public static final List<IOrder> getToOpenOrders(Instrument instrument){
		return TO_OPEN_ORDERS.getOrders(instrument);
	}
	
	public static final int getToOpenOrdersCount(){
		return TO_OPEN_ORDERS.getOrdersCount();
	}
	
	public static final void clearToCloseOrders(){
		TO_CLOSE_ORDERS.clear();
	}
	
	public static final void putToCloseOrders(Instrument instrument, IOrder order){
		TO_CLOSE_ORDERS.putOrder(instrument, order);
	}
	
	public static final void putToCloseOrders(Instrument instrument, List<IOrder> orders){
		TO_CLOSE_ORDERS.putOrders(instrument, orders);
	}
	
	public static final List<IOrder> getToCloseOrders(Instrument instrument){
		return TO_CLOSE_ORDERS.getOrders(instrument);
	}
	
	public static final void releaseFlagForToCloseOrders(Instrument instrument){
		TO_CLOSE_ORDERS.releaseFlag(instrument);
	}
	
	public static final int getToCloseOrdersCount(){
		return TO_CLOSE_ORDERS.getOrdersCount();
	}
	
	/**
	 * Класс для хранения списка ордеров над которыми планируется выполнять какие-то действия (открыть / модифицировать / закрыть)
	 * @author artem
	 *
	 */
	private static class OrderList {
		
		private final String ID;
		/**
		 * Флаги для синхронизированного доступа к спискам ордеров
		 */
		private final ConcurrentMap<Instrument, Integer> flags = new ConcurrentHashMap<Instrument, Integer>();
		/**
		 * Списки ордеров
		 */
		private final Map<Instrument, List<IOrder>> orders = new HashMap<Instrument, List<IOrder>>();
		{
			for(Instrument instrument : Instrument.values()){
				orders.put(instrument, new ArrayList<IOrder>());
			}
		}
		
		private OrderList(String iD) {
			super();
			ID = iD;
		}
		
		/**
		 * Метод возвращает список ордеров, соответствующих инструменту и блокирует получение этих ордеров другими потоками до тех пор, пока
		 * не будет вызван метод {@link #releaseFlag(Instrument)}
		 * @param instrument
		 * @return
		 */
		private final List<IOrder> getOrders(Instrument instrument){
			if(flags.putIfAbsent(instrument, 1) == null){
				logger.debug("{} - {}. Return list of orders for Instrument.", instrument.toString(), this.getClass().getName());
				return orders.get(instrument);
			}
			logger.info("{} - {}. List of orders for Instrument is busy.", instrument.toString(), this.getClass().getName());
			return Collections.emptyList();
		}
		
		private final void clear(){
			int counter = 0;
			
			for(Instrument instrument : orders.keySet()){
					
				int previousSize = counter;
					
				while(previousSize == counter){
					if(flags.putIfAbsent(instrument, 1) == null){
						orders.get(instrument).clear();
						releaseFlag(instrument);
						counter++;
					}else{
						continue;
					}
				}
			}
		}
		
		private final boolean releaseFlag(Instrument instrument){
			logger.debug("{} - {}. Flag for Instrument released.", instrument.toString(), this.getClass().getName());
			return flags.remove(instrument, 1);
		}
		
		private final void putOrder(Instrument instrument, IOrder order){
			putOrders(instrument, Arrays.asList(order));
		}
		
		private final void putOrders(Instrument instrument, List<IOrder> orders){
			logger.info("{}. Adding {} orders to {} list", new Object[]{instrument.toString(), orders.size(), ID});
			while(flags.get(instrument) == 1){
				try {
					Thread.sleep(200L);
				} catch (InterruptedException e) {
					logger.error("Error when sleeping", e);
					continue;
				}
			}
			this.orders.get(instrument).addAll(orders);
		}
		
		private final int getOrdersCount(){
			int counter = 0;
			
			for(Instrument instrument : orders.keySet()){
				counter += orders.get(instrument).size();
			}
			
			return counter;
		}
		
	}

}
